#include <chrono>
#include <condition_variable>
#include <cstring>
#include <functional>
#include <iostream>
#include <map>
#include <memory>
#include <mutex>
#include <queue>
#include <thread>

#include <uv.h>

#define _UVERR(call, err) std::cerr << call << " failed because " << uv_strerror(err) << std::endl
#define UVERR(call, err) { if (err) { _UVERR(call, err); } }
#define UVERRRET(call, err) { if (err) { _UVERR(call, err); return; } }
#define UVERRRETVAL(call, err) { if (err) { _UVERR(call, err); return err; } }
#define UVCALL(call, ...) { if (auto err = call(__VA_ARGS__)) { UVERR(#call, err); } }
#define UVCALLRET(call, ...) { if (auto err = call(__VA_ARGS__)) { UVERRRET(#call, err); } }
#define UVCALLRETVAL(call, ...) { if (auto err = call(__VA_ARGS__)) { UVERRRETVAL(#call, err); } }

template<class T>
class SemQueue {
public:
    void PopFront(T &t)
    {
        std::unique_lock lock(mutex_);
        if (queue_.empty()) {
            cv_.wait(lock, [this]() { return !queue_.empty(); });
        }
        t = queue_.front();
        queue_.pop();
    }

    void Push(const T &t)
    {
        std::unique_lock lock(mutex_);
        queue_.push(std::move(t));
        cv_.notify_all();
    }

private:
    std::queue<T> queue_;
    std::mutex mutex_;
    std::condition_variable cv_;
};

class TaskExecutor {
public:
    using Task = std::function<void()>;
    enum class TaskType : int {
        UI,
        GPU,
        JS,
        BG,
    };

    TaskExecutor()
    {
        UVCALLRET(uv_loop_init, &loop_);
    }

    int Run()
    {
        UVCALLRETVAL(uv_signal_init, &loop_, &signal_);
        UVCALLRETVAL(uv_signal_start, &signal_, OnSignal, SIGINT);
        UVCALLRETVAL(uv_run, &loop_, UV_RUN_DEFAULT);
        return 0;
    }

    int Stop()
    {
        for (auto &[_, taskThread] : threads_) {
            UVCALL(uv_timer_stop, &taskThread.waitingTimer);
            taskThread.runningTasks.Push(nullptr);
        }

        uv_stop(&loop_);
        for (auto &[_, taskThread] : threads_) {
            taskThread.thread->join();
        }

        return 0;
    }

    int PostTask(const Task &task, const TaskType &tasktype, int timeoutMs = 0)
    {
        auto &taskThread = threads_[static_cast<int>(tasktype)];
        if (taskThread.thread == nullptr) {
            taskThread.thread = std::make_unique<std::thread>(
                std::bind(&TaskExecutor::TaskMain, this, tasktype));
            if (taskThread.thread == nullptr) {
                std::cerr << "make_unique thread failed with " << strerror(errno) << std::endl;
                return 1;
            }
        }

        if (timeoutMs == 0) {
            taskThread.runningTasks.Push(task);
        } else {
            return PostDelayTask(task, tasktype, timeoutMs);
        }

        return 0;
    }

private:
    void TaskMain(const TaskType &tasktype)
    {
        auto &taskThread = threads_[static_cast<int>(tasktype)];
        while (true) {
            Task task;
            taskThread.runningTasks.PopFront(task);
            if (task == nullptr) {
                break;
            }
            task();
        }
    }

    int PostDelayTask(const Task &task, const TaskType &tasktype, int timeoutMs)
    {
        auto targetMs = GetNow() + timeoutMs;
        auto &taskThread = threads_[static_cast<int>(tasktype)];
        std::lock_guard lock(taskThread.waitingMutex);
        auto nowMs = GetNow();
        if (targetMs <= nowMs) {
            taskThread.runningTasks.Push(task);
            return 0;
        }

        auto it = taskThread.waitingTasks.find(targetMs);
        if (it == taskThread.waitingTasks.end()) {
            if (taskThread.waitingTime == 0) {
                UVCALL(uv_timer_init, &loop_, &taskThread.waitingTimer);
                taskThread.waitingTimer.data = &taskThread;
            }

            bool needUpdate = taskThread.waitingTime > nowMs && taskThread.waitingTime > targetMs;
            needUpdate = needUpdate || taskThread.waitingTime < nowMs;
            if (needUpdate) {
                taskThread.waitingTime = targetMs;
                UVCALL(uv_timer_start, &taskThread.waitingTimer, OnTimer, targetMs - nowMs, 0);
            }
        }

        taskThread.waitingTasks[targetMs].push_back(task);
        return 0;
    }

    static inline int64_t GetNow()
    {
        return std::chrono::duration_cast<std::chrono::milliseconds>(
            std::chrono::steady_clock::now().time_since_epoch()).count();
    }

    static void OnTimer(uv_timer_t *timer)
    {
        auto taskThread = reinterpret_cast<struct TaskThread *>(timer->data);
        std::lock_guard lock(taskThread->waitingMutex);
        const auto &nowMs = GetNow();

        std::vector<int64_t> eraseTimes;
        for (const auto &[time, tasks] : taskThread->waitingTasks) {
            if (time > nowMs) {
                taskThread->waitingTime = time;
                UVCALL(uv_timer_start, timer, OnTimer, time - nowMs, 0);
                break;
            }

            for (const auto &task : tasks) {
                taskThread->runningTasks.Push(task);
            }
            eraseTimes.push_back(time);
        }

        for (const auto &time : eraseTimes) {
            taskThread->waitingTasks.erase(time);
        }
    }

    static void OnSignal(uv_signal_t *signal, int32_t signum)
    {
        if (signum == SIGINT) {
            UVCALL(uv_signal_stop, signal);
            GetInstance(signal).Stop();
        }
    }

    template<typename T>
    static TaskExecutor &GetInstance(T *that)
    {
        return *reinterpret_cast<TaskExecutor *>(that->data);
    }

    struct TaskThread {
        SemQueue<Task> runningTasks;
        std::unique_ptr<std::thread> thread = nullptr;

        // waitingTasks Props
        std::mutex waitingMutex;
        std::map<int64_t, std::vector<Task>> waitingTasks;
        int64_t waitingTime = 0;
        uv_timer_t waitingTimer = { .data = nullptr };
    };

    uv_loop_t loop_ = {.data = this};
    uv_signal_t signal_ = {.data = this};
    std::map<int, struct TaskThread> threads_;
};

int main()
{
    TaskExecutor executor;
    executor.PostTask([]() {
        std::cout << "UI: now thread id: " << std::this_thread::get_id() << std::endl;
    }, TaskExecutor::TaskType::UI);
    executor.PostTask([]() {
        std::cout << "GPU: now thread id: " << std::this_thread::get_id() << std::endl;
    }, TaskExecutor::TaskType::GPU, 0);
    executor.PostTask([]() {
        std::cout << "JS: now thread id: " << std::this_thread::get_id() << std::endl;
    }, TaskExecutor::TaskType::JS, 100);
    executor.PostTask([]() {
        std::cout << "BG: now thread id: " << std::this_thread::get_id() << std::endl;
    }, TaskExecutor::TaskType::BG, 1000);

    executor.PostTask([]() {
        std::cout << "UI: now thread id: " << std::this_thread::get_id() << std::endl;
    }, TaskExecutor::TaskType::UI, 2000);
    executor.PostTask([]() {
        std::cout << "GPU: now thread id: " << std::this_thread::get_id() << std::endl;
    }, TaskExecutor::TaskType::GPU, 3000);
    executor.PostTask([]() {
        std::cout << "JS: now thread id: " << std::this_thread::get_id() << std::endl;
    }, TaskExecutor::TaskType::JS, 4000);
    executor.PostTask([]() {
        std::cout << "BG: now thread id: " << std::this_thread::get_id() << std::endl;
    }, TaskExecutor::TaskType::BG, 5000);
    return executor.Run();
}
